-------------Zoo Collector-------------
A 4am crack                  2017-04-17
---------------------------------------

Name: Zoo Collector
Genre: educational
Year: 1987
Publisher: National Geographic Society
Platform: Apple //e or later
Media: double-sided 5.25-inch floppy
OS: Pronto-DOS
Previous cracks: none
Similar cracks:
  #1141 Zoo Builder

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but the copy swings to a
  high track and hangs with the drive
  motor on

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Copy ][+ nibble editor
  nothing suspicious

Disk Fixer
  track 0 looks like Pronto-DOS loader,
  but it jumps to $B3C3 after loading
  the RWTS (instead of $9D84)

Why didn't any of my copies work?
  I don't know. Not a lot to go on yet.
  Presumably if there was some
  structural protection I would have
  noticed read errors. Hanging with the
  drive motor on could mean anything.

Next steps:

  1. Search for common markers of a
     runtime protection check
  2. If that fails, trace the boot
  3. If that fails, I dunno, go feed
     the ducks or something?

                   ~

               Chapter 1
 In Which The Ducks Will Have To Wait


Turning to my trusty Disk Fixer sector
editor, I search for "BD 89 C0", a
common instruction to turn on the disk
drive motor. It finds several matches,
but nothing that looks like the start
of a protection check. So we get to
start at the beginning.

T00,S00 is a standard DOS 3.3-style
bootloader. It loads most of the rest
of track 0 into $B600+ and jumps to
$B700, stored on T00,S01, to load the
rest of the disk.

                 --v--
T00,S01
----------- DISASSEMBLY MODE ----------
0000:8E E9 B7       STX   $B7E9
0003:8E F7 B7       STX   $B7F7
0006:A9 01          LDA   #$01
0008:8D F8 B7       STA   $B7F8
000B:8D EA B7       STA   $B7EA
000E:AD E0 B7       LDA   $B7E0
0011:8D E1 B7       STA   $B7E1

; start reading DOS on T02,S00 -- looks
; like Pronto-DOS
0014:A9 02          LDA   #$02
0016:8D EC B7       STA   $B7EC
0019:A9 00          LDA   #$00
001B:8D ED B7       STA   $B7ED

; $B7E7 is $B4, so the first sector is
; read into $B300
001E:AC E7 B7       LDY   $B7E7
0021:88             DEY
0022:8C F1 B7       STY   $B7F1

; RWTS read command
0025:A9 01          LDA   #$01
0027:8D F4 B7       STA   $B7F4

; set up globals (normal)
002A:8A             TXA
002B:4A             LSR
002C:4A             LSR
002D:4A             LSR
002E:4A             LSR
002F:AA             TAX
0030:A9 00          LDA   #$00
0032:9D F8 04       STA   $04F8,X
0035:9D 78 04       STA   $0478,X

; read DOS into memory (normal)
0038:20 93 B7       JSR   $B793

; reset stack (normal)
003B:A2 FF          LDX   #$FF
003D:9A             TXS
003E:8E EB B7       STX   $B7EB

; machine initialization (not shown,
; but this is definitely Pronto-DOS --
; other DOS variants put this code
; somewhere else)
0041:20 69 BA       JSR   $BA69
0044:20 89 FE       JSR   $FE89

; and continue in a strange spot
0047:4C C3 B3       JMP   $B3C3

                 --^--

Standard unprotected Pronto-DOS would
jump to $9D84 at this point, but this
disk has other ideas.

$B300 was on T02,S00, so let's jump
over there and continue disassembly.

                 --v--
T02,S00
----------- DISASSEMBLY MODE ----------
; set up another RWTS read: track $02
00C3:A9 02          LDA   #$02
00C5:8D EC B7       STA   $B7EC

; sector 1
00C8:A9 01          LDA   #$01
00CA:8D ED B7       STA   $B7ED

; into $B400
00CD:A9 B4          LDA   #$B4
00CF:8D F1 B7       STA   $B7F1
00D2:A9 00          LDA   #$00
00D4:8D F3 B7       STA   $B7F3
00D7:8D EB B7       STA   $B7EB

; and go
00DA:A9 B7          LDA   #$B7
00DC:A0 E8          LDY   #$E8
00DE:20 B5 B7       JSR   $B7B5

; and decrypt it (!)
00E1:A0 00          LDY   #$00
00E3:B9 12 B4       LDA   $B412,Y
00E6:49 4C          EOR   #$4C
00E8:99 12 B4       STA   $B412,Y
00EB:C8             INY
00EC:C0 FF          CPY   #$FF
00EE:D0 F3          BNE   $00E3

; munge reset vector
00F0:A9 00          LDA   #$00
00F2:8D F4 03       STA   $03F4

; and continue in decrypted code
00F5:4C 12 B4       JMP   $B412

                 --^--

Here's T02,S01:

                 --v--

-------------- DISK EDIT --------------
TRACK $02/SECTOR $01/VOLUME $FE/BYTE$12
---------------------------------------
$00: A0 00 B9 12 20 49 4C 99    @9R IL.
$08: 12 20 C8 C0 FF D0 F3 60   R H@Ps
$10: EA EA>E5<6F C1 A0 FB E5   jje/A {e
$18: 4C C9 48 C1 B8 FB C1 A7   LIHA8{A'
$20: FB 6C A7 F8 6C BE F8 E5   {,'x,>xe
$28: 4C C9 04 F1 C5 8C F1 C2   LIDqE.qB
$30: 8C 6C 08 F5 E9 61 89 48   .,Hui!.H
$38: 9C BB F1 C2 8C F1 C0 8C   .;qB.q@.
$40: 5C B7 85 B3 9C B8 EC 4C   \7.3.8lL
$48: 84 F1 C2 8C F1 C0 8C 5C   .qB.q@.\
$50: B7 85 99 9C BF 8C 4B 9C   7...?.K.
$58: F5 AA 48 E5 5C 89 48 9C   u*He\.H.
$60: 9C E5 4C C9 48 6C 08 F5   .eLIH,Hu
$68: E9 61 89 48 9C BB F1 C0   i!.H.;q@
$70: 8C 5C B7 85 E6 9C BB F1   .\7.f.;q
$78: C0 8C 5C B7 85 A7 9C A2   @.\7.'."
---------------------------------------
BUFFER 0/SLOT 6/DRIVE 1/MASK OFF/NORMAL

---------------------------------------
COMMAND :

                 --^--

Not much to see, since everything after
byte $12 is encrypted (in the 80s kind
of way where it's just XOR'd with a
constant byte, but still). Amusingly,
there is unencrypted code at byte $00
which decrypts the rest of the sector
in the same way as the bootloader. It
assumes the code is stored at $2000
instead of $B400. Maybe debugging code
left by the original developer?

At this point, you might think I would
need to switch to boot tracing the old
fashioned way so I could see this
decrypted code. But Disk Fixer is very
powerful! It has a "MASK" mode that
allows me to XOR every byte of a sector
with a constant byte -- exactly as the
bootloader is doing after it reads this
sector into memory.

Pressing "X" in Disk Fixer lets me set
the mask:

                 --v--

MASK WITH ADD $00/EOR $4C/AND $FF/OR$00
                      ^^^
                       +--- change this

                 --^--

Then pressing <Ctrl-T> applies the mask
to the current sector:

                 --v--

-------------- DISK EDIT --------------
TRACK $02/SECTOR $01/VOLUME $FE/BYTE$12
---------------------------------------
$00: EC 4C F5 5E 6C 05 00 D5   lLu^,E@U
$08: 5E 6C 84 8C B3 9C BF 2C   ^,..3.?,
$10: A6 A6>A9<23 8D EC B7 A9   &&)#.l7)
$18: 00 85 04 8D F4 B7 8D EB   @.D.t7.k
$20: B7 20 EB B4 20 F2 B4 A9   7 k4 r4)
$28: 00 85 48 BD 89 C0 BD 8E   @.H=.@=.
$30: C0 20 44 B9 A5 2D C5 04   @ D9%-ED
$38: D0 F7 BD 8E C0 BD 8C C0   Pw=.@=.@
$40: 10 FB C9 FF D0 F4 A0 00   P{I.Pt @
$48: C8 BD 8E C0 BD 8C C0 10   H=.@=.@P
$50: FB C9 D5 D0 F3 C0 07 D0   {IUPs@GP
$58: B9 E6 04 A9 10 C5 04 D0   9fD)PEDP
$60: D0 A9 00 85 04 20 44 B9   P)@.D D9
$68: A5 2D C5 04 D0 F7 BD 8C   %-EDPw=.
$70: C0 10 FB C9 AA D0 F7 BD   @P{I*Pw=
$78: 8C C0 10 FB C9 EB D0 EE   .@P{IkPn
---------------------------------------
BUFFER 0/SLOT 6/DRIVE 1/MASK ON /NORMAL

---------------------------------------
COMMAND :

                 --^--

And now we can list the encrypted
routine at byte $12.

                   ~

               Chapter 2
        The Belly Of The Beast


The protection check starts at byte $12
(loaded into memory at $B412):

                 --v--

T02,S01
----------- DISASSEMBLY MODE ----------
; Hmm, seeking to track $23 perhaps?
; That would explain why even my EDD
; bit copy failed -- I didn't copy
; track $23!
0012:A9 23          LDA   #$23
0014:8D EC B7       STA   $B7EC

; 0 = RWTS seek command
0017:A9 00          LDA   #$00
0019:85 04          STA   $04
001B:8D F4 B7       STA   $B7F4

; 0 also = disk volume wildcard
001E:8D EB B7       STA   $B7EB

; get address of RWTS parameter table
; and call RWTS entry point to seek
; (not shown)
0021:20 EB B4       JSR   $B4EB
0024:20 F2 B4       JSR   $B4F2
0027:A9 00          LDA   #$00
0029:85 48          STA   $48

; Oh look! Here's that "BD 89 C0"
; instruction I was looking for earlier
; but couldn't find (because it was
; encrypted)
002B:BD 89 C0       LDA   $C089,X
002E:BD 8E C0       LDA   $C08E,X

; look for the next available address
; field
0031:20 44 B9       JSR   $B944

; compare the sector we found against
; the counter we initialized in zp$04
; earlier
0034:A5 2D          LDA   $2D
0036:C5 04          CMP   $04

; loop until we find the address field
; for track $23, sector $00
0038:D0 F7          BNE   $0031

; skip to sync byte (#$FF)
003A:BD 8E C0       LDA   $C08E,X
003D:BD 8C C0       LDA   $C08C,X
0040:10 FB          BPL   $003D
0042:C9 FF          CMP   #$FF
0044:D0 F4          BNE   $003A

; skip an exact number of nibbles and
; look for #$D5
0046:A0 00          LDY   #$00
0048:C8             INY
0049:BD 8E C0       LDA   $C08E,X
004C:BD 8C C0       LDA   $C08C,X
004F:10 FB          BPL   $004C
0051:C9 D5          CMP   #$D5
0053:D0 F3          BNE   $0048

; if not found in correct location, try
; again from the top (Y register is the
; nibble counter)
0055:C0 07          CPY   #$07
0057:D0 B9          BNE   $0012

; do this for all sectors
0059:E6 04          INC   $04
005B:A9 10          LDA   #$10
005D:C5 04          CMP   $04
005F:D0 D0          BNE   $0031

; start over on sector 0
0061:A9 00          LDA   #$00
0063:85 04          STA   $04
0065:20 44 B9       JSR   $B944
0068:A5 2D          LDA   $2D
006A:C5 04          CMP   $04
006C:D0 F7          BNE   $0065

; skip to address epilogue
006E:BD 8C C0       LDA   $C08C,X
0071:10 FB          BPL   $006E
0073:C9 AA          CMP   #$AA
0075:D0 F7          BNE   $006E
0077:BD 8C C0       LDA   $C08C,X
007A:10 FB          BPL   $0077
007C:C9 EB          CMP   #$EB
007E:D0 EE          BNE   $006E

; skip to sync byte (#$FF)
0080:BD 8C C0       LDA   $C08C,X
0083:10 FB          BPL   $0080
0085:C9 FF          CMP   #$FF
0087:D0 F7          BNE   $0080

; skip an exact number of nibbles and
; look for #$D5
0089:A0 00          LDY   #$00
008B:C8             INY
008C:BD 8E C0       LDA   $C08E,X
008F:BD 8C C0       LDA   $C08C,X
0092:10 FB          BPL   $008F
0094:C9 D5          CMP   #$D5
0096:D0 F3          BNE   $008B

; if not found in correct location, try
; again from the top (Y register is the
; nibble counter again)
0098:C0 10          CPY   #$10
009A:D0 C5          BNE   $0061

; do this for several other sectors
009C:E6 04          INC   $04
009E:A9 0A          LDA   #$0A
00A0:20 A8 FC       JSR   $FCA8
00A3:A9 04          LDA   #$04
00A5:C5 04          CMP   $04
00A7:D0 04          BNE   $00AD
00A9:E6 04          INC   $04
00AB:D0 B8          BNE   $0065
00AD:A9 0F          LDA   #$0F
00AF:C5 04          CMP   $04
00B1:D0 BB          BNE   $006E
00B3:20 44 B9       JSR   $B944
00B6:A5 2D          LDA   $2D
00B8:C9 0F          CMP   #$0F
00BA:D0 F7          BNE   $00B3

; read data field (not shown)
00BC:20 DC B8       JSR   $B8DC

; look for #$A5 nibble in a specific
; place
00BF:A0 00          LDY   #$00
00C1:C8             INY
00C2:BD 8E C0       LDA   $C08E,X
00C5:BD 8C C0       LDA   $C08C,X
00C8:10 FB          BPL   $00C5
00CA:C9 A5          CMP   #$A5
00CC:D0 F3          BNE   $00C1

; if not found in correct location, try
; again from the top (Y register is the
; nibble counter again)
00CE:C0 2B          CPY   #$2B
00D0:D0 E1          BNE   $00B3

; look for #$D5 nibble in a specific
; place
00D2:A0 00          LDY   #$00
00D4:C8             INY
00D5:BD 8E C0       LDA   $C08E,X
00D8:BD 8C C0       LDA   $C08C,X
00DB:10 FB          BPL   $00D8
00DD:C9 D5          CMP   #$D5
00DF:D0 F3          BNE   $00D4

; if not found in correct location, try
; again from the top (Y register is the
; nibble counter again)
00E1:C0 5D          CPY   #$5D
00E3:D0 CE          BNE   $00B3

; success path falls through to here --
; turn off the drive motor and jump
; forward
00E5:BD 88 C0       LDA   $C088,X
00E8:4C F5 B4       JMP   $B4F5

; set RUN flag to disable user control
; in case they manage to get to a BASIC
; prompt
00F5:A9 FF          LDA   #$FF
00F7:85 D6          STA   $D6

; continue with standard boot sequence
00F9:4C 84 9D       JMP   $9D84

                 --^--

My copy never makes it this far. It's
stuck in an infinite loop, trying to
find a precisely organized track $23
(and failing).

It looks like I should be able to skip
over the protection check by jumping
straight to $9D84 instead of $B3C3 (all
the way back at $B747).

T00,S01,$48: C3B3 -> 849D

]PR#6
...works...

Side B is unprotected.

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 1142
------------------EOF------------------
